use std::env;
use std::time::Duration;

use axum::{Json, Router};
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use dotenvy::dotenv;
use redis::{Client, Commands};
use sea_orm::{ConnectOptions, Database, DatabaseConnection, EntityTrait};
use serde::{Deserialize, Serialize};
use tracing::log;

use rust_database::AppState;
use rust_database::prelude::XxlJobUser;
use rust_database::xxl_job_user::Model;

#[tokio::main]
async fn main() {
    let mysql = init_mysql().await;
    let redis = init_redis().await;

    // AppState 共享数据使用参考 axum 官方示例： https://docs.rs/axum/latest/axum/#sharing-state-with-handlers
    // 创建一个共享的状态，包含数据库连接池和 Redis 连接
    // let shared_state = Arc::new(AppState { mysql, redis });
    let shared_state = AppState { mysql, redis };

    // build our application with a route
    let app = Router::new()
        .route("/mysql", get(test_mysql))
        .route("/redis", get(test_redis))
        .with_state(shared_state);

    // 创建一个 IPv4 格式的服务地址，并在端口 3000 上进行全局监听
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    // 使用 hyper 运行我们的 HTTP 服务器，并绑定上面的监听和路由
    axum::serve(listener, app).await.unwrap();
}

/// sea_orm 官方网站： https://www.sea-ql.org/SeaORM/docs/install-and-config/connection/  此处为连接配置
// 初始化数据库连接池
pub async fn init_mysql() -> DatabaseConnection {
    // 此处部分配置参考 sea_orm 官方示例： https://github.com/SeaQL/sea-orm/blob/master/examples/axum_example/api/src/lib.rs
    // 设置日志级别
    env::set_var("RUST_LOG", "debug");

    // 加载根目录下的 .env 文件配置到环境变量，而不是当前目录
    dotenv().ok();

    // env::vars().for_each(|x| println!("{:?}", x));

    let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file");
    println!("Database URL: {}", db_url);

    // 通过数据库连接地址创建连接池配置对象
    let mut opt = ConnectOptions::new(db_url);

    // 配置连接池参数
    opt.max_connections(100)
        .min_connections(5)
        .connect_timeout(Duration::from_secs(8))
        .acquire_timeout(Duration::from_secs(8))
        .idle_timeout(Duration::from_secs(8))
        .max_lifetime(Duration::from_secs(8))
        // 是否打印SQL执行日志
        .sqlx_logging(true)
        // 如果开启SQL日志、对应设置SQL日志级别
        .sqlx_logging_level(log::LevelFilter::Info)
        // 设置默认连接的数据库
        .set_schema_search_path("blog_cloud");

    // 开始连接数据库
    // 在后台，会创建了一个 sqlx::Pool，并由 DatabaseConnection 拥有。每次调用 execute 或 query_one/all 时，都会从池中获取并释放一个连接。当您等待多个查询时，它们将并行执行。
    let db = Database::connect(opt).await.expect("Failed to connect to database");

    // 测试数据库连接是否成功
    let result = db.ping().await.is_ok();
    println!("Database connection test result: {}", result);

    db
}

/// redis 官方网站： https://docs.rs/redis/0.26.0/redis/  此处为连接配置
// redis 数据库连接示例
async fn init_redis() -> Client {
    // 连接 redis  注意这里的密码
    let client = redis::Client::open("redis://:479368@127.0.0.1:6379/").unwrap();
    // // 获取一个连接
    // let mut conn = client.get_connection().unwrap();
    // // 扔掉结果，只要确保它不会失败
    // let _ : () = conn.set("龙茶清欢2025", "13647933851").unwrap();
    client
}

pub async fn find_all_mysql(State(state): State<AppState>) -> ApiResponse<String> {
    // 使用 state.db 或 state.config
    let db = state.mysql;

    let users = XxlJobUser::find().all(&db).await.unwrap();
    users.iter().for_each(|user| {
        println!("the user is {:?}", user);
    });
    // 使用美化的 JSON 格式打印用户信息。实际开发不要使用 to_string_pretty
    let users = serde_json::to_string_pretty(&users).unwrap();
    ApiResponse::new(200, "success".to_string(), Some(users))
}

#[derive(Serialize, Deserialize, Debug)]
struct User {
    id: u32,
    name: String,
}

// 处理函数，使用 AppState 中的 Redis 客户端
async fn store_user_to_redis(State(state): State<AppState>) -> ApiResponse<String> {
    // 获取连接
    // let mut conn = state.redis.get_connection().unwrap();
    let mut conn = state.redis.get_connection().unwrap();

    let _: () = conn.set("test", "test_data").unwrap();
    let data: String = conn.get("test").unwrap();
    println!("Redis get test: {}", data);

    // // 定义一个用户
    let user = User {
        id: 1,
        name: "John Doe".into(),
    };
    // 设置值
    let json_user = serde_json::to_string(&user).unwrap();
    println!("json_user: {:#?}", json_user);

    let _: () = conn.set("user", json_user).unwrap();


    let json_user: String = conn.get("user").unwrap();
    let user: User = serde_json::from_str(&json_user).unwrap();
    println!("Redis get user: {:#?}", user);

    ApiResponse::new(200, "success".to_string(), Option::from(serde_json::to_string(&user).unwrap()))
}


// 压力测试数据库连接
async fn test_mysql(State(state): State<AppState>) -> ApiResponse<Vec<Model>> {
    let db = state.mysql;
    let users = XxlJobUser::find().all(&db).await.unwrap();
    ApiResponse::new(200, "success".to_string(), Some(users))
}

// 压力测试 redis 连接
async fn test_redis(State(state): State<AppState>) -> ApiResponse<String> {
    // 获取连接
    let mut conn = state.redis.get_connection().unwrap();
    // unwrap 的返回值是 Option<String>，如果 key 不存在，则返回 None
    let json_user: String = conn.get("user").unwrap();

    ApiResponse::new(200, "success".to_string(), Some(json_user))
}


/// 自定义响应类型
/*
axum 已经实现了多种响应，比如纯文本、HTML、JSON 及 自定义响应头(response header)。除了这些 axum 内置的响应之外，我们还将讨论如何将自己定义的结构体，作为响应返回给客户端。
axum 有句话说的是：
    Anything that implements IntoResponse can be returned from a handler
大意就是：凡是实现了 IntoResponse trait 的，都可作为 handler 的返回值，也就是我们所说的响应。
官方已经对若干常用数据类型实现了该 trait，亦是说，这些数据类型可以直接作为请求的响应进行返回。
对于我们自己的数据类型，比如自定义的 struct，也只需要实现 IntoResponse，就可以直接作为响应进行返回了。
*/

// 定义一个结构体来封装你的响应数据。这里我们定义一个 ApiResponse 结构体，它包含三个字段：status（状态码），message（消息）和 data（可选的数据）
#[derive(Debug, Serialize, Deserialize)]
pub struct ApiResponse<T> {
    pub status: u16,
    pub message: String,
    pub data: Option<T>,
}

// 自定义响应类型的实现
impl<T> ApiResponse<T> {
    fn new(status: u16, message: String, data: Option<T>) -> Self {
        Self {
            status,
            message,
            data,
        }
    }
}
// 为自定义响应类型 ApiResponse<T> 实现 IntoResponse trait，用于转换为 axum::response::Response 类型
impl<T> IntoResponse for ApiResponse<T>
where
    T: serde::Serialize,
{
    // 实现 IntoResponse trait 的 into_response 方法，用于转换为 axum::response::Response 类型
    // 此处的 self 代表 ApiResponse<T> 结构体
    fn into_response(self) -> Response {
        let body = Json(&self);
        // 这里我们将自定义的结构体 ApiResponse<T> 转换为 JSON 格式的响应，并设置响应头 Content-Type: application/json
        (
            StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
            body,
            // 这里我们设置响应头 Content-Type: application/json
        ).into_response()
    }
}

// axum 请求响应处理的官方文档： https://docs.rs/axum/latest/axum/response/index.html